#include <iio.h>
#include <ad9361.h>
#include <getopt.h>
#include <stdio.h>
#include <signal.h>
#include <thread>
#include <mutex>
#include <deque>
#include <condition_variable>
#include <cstring>
#include "bledecoder.hpp"
#include "packetsender.hpp"
#define BUF_SIZE 16384
#define BUF_LEN 2
packetsender * sender=nullptr;
iio_context *ctx = nullptr;
bledecoder* decoder= nullptr;
int channel = 39;
volatile bool do_exit = false;
volatile bool dump = true;
int16_t buffer[BUF_LEN][BUF_SIZE*2];
size_t buf_count,buf_head;
//std::deque<int16_t> queue;
std::mutex mutex;
std::condition_variable cond;

#ifdef _MSC_VER
	BOOL WINAPI
sighandler(int signum)
{
	if (CTRL_C_EVENT == signum) {
		fprintf(stdout, "Caught signal %d\n", signum);
		do_exit = true;
		return TRUE;
	}
	return FALSE;
}
#else
void sigint_callback_handler(int signum)
{
	fprintf(stdout, "Caught signal %d\n", signum);
	do_exit = true;
}
#endif


void PacketCallback(lell_packet _packet) {

	if(dump){
		for (int i = 6; i < 6 + _packet.length; ++i)
			printf(" %02x", _packet.symbols[i]);
		printf("\n");

		printf("type:%d \n",_packet.adv_type);

		printf("    CRC:  ");
		for (int i = 0; i < 3; ++i)
			printf(" %02x", _packet.symbols[6 + _packet.length + i]);
		printf("\n");
	}
	if (sender)
		sender->send(_packet);
}


static struct iio_context * autodetect_context(void)
{
	struct iio_scan_context *scan_ctx;
	struct iio_context_info **info;
	struct iio_context *ctx = NULL;
	unsigned int i;
	ssize_t ret;

	scan_ctx = iio_create_scan_context(NULL, 0);
	if (!scan_ctx) {
		fprintf(stderr, "Unable to create scan context\n");
		return NULL;
	}

	ret = iio_scan_context_get_info_list(scan_ctx, &info);
	if (ret < 0) {
		char err_str[1024];
		iio_strerror(-ret, err_str, sizeof(err_str));
		fprintf(stderr, "Scanning for IIO contexts failed: %s\n", err_str);
		goto err_free_ctx;
	}

	if (ret == 0) {
		printf("No IIO context found.\n");
		goto err_free_info_list;
	}

	if (ret == 1) {
		printf("Using auto-detected IIO context at URI \"%s\"\n",
				iio_context_info_get_uri(info[0]));
		ctx = iio_create_context_from_uri(iio_context_info_get_uri(info[0]));
	}
	else {
		fprintf(stderr, "Multiple contexts found. Please select one using --uri:\n");

		for (i = 0; i < (size_t)ret; i++) {
			fprintf(stderr, "\t%d: %s [%s]\n", i,
					iio_context_info_get_description(info[i]),
					iio_context_info_get_uri(info[i]));
		}
	}

err_free_info_list:
	iio_context_info_list_free(info);
err_free_ctx:
	iio_scan_context_destroy(scan_ctx);

	return ctx;
}

static const struct option options[] = {
	{ "help", no_argument, 0, 'h' },
	{ "network", required_argument, 0, 'n' },
	{ "uri", required_argument, 0, 'u' },
	{ "capture", required_argument, 0, 'r' },
	{ "channel", required_argument, 0, 'c' },
	{ "access_addr", required_argument, 0, 'a' },
	{ "send_addr", required_argument, 0, 's' },
	{ 0, 0, 0, 0 },
};

static const char *options_descriptions[] = {
	"Show this help and quit.",
	"Use the network backend with the provided hostname.",
	"Use the context at the provided URI.",
	"Capture packets to PcapNG file[Default: OFF]",
	"Set a fixed PlutoSDR channel [Default: 39].",
	"Set sniffed Access address [Default: 8E89BED6].",
	"Send packets to IP address [Default: 192.168.2.10]."
};

static void usage(void)
{
	unsigned int i;
	for (i = 0; options[i].name; i++)
		printf("\t-%c, --%s\n\t\t\t%s\n",
				options[i].val, options[i].name,
				options_descriptions[i]);
}

void thread_decode(){
	
	
	while((do_exit==false)){

		std::unique_lock<std::mutex> lock(mutex);	
		while(buf_count==0 && (do_exit==false))
			cond.wait(lock);
		buf_count--;
		buf_head = (buf_head + 1) % BUF_LEN;
		decoder->receiver(buffer[buf_head], BUF_SIZE * 2);
		
	}
}

void thread_recv() {

	iio_channel* phy_chn = iio_device_find_channel(iio_context_find_device(ctx, "ad9361-phy"), "voltage0", false);
	iio_channel_attr_write(phy_chn, "rf_port_select", "A_BALANCED");
	iio_channel_attr_write_longlong(phy_chn, "rf_bandwidth", 2e6);
	iio_channel_attr_write_longlong(phy_chn, "sampling_frequency", 2e6);
	iio_channel_attr_write(phy_chn, "gain_control_mode", "slow_attack");

	iio_channel* lo_chn = iio_device_find_channel(iio_context_find_device(ctx, "ad9361-phy"), "altvoltage0", true);
	iio_channel_attr_write_longlong(lo_chn, "frequency", decoder->get_channel_freq(channel));

	ad9361_set_bb_rate(iio_context_find_device(ctx, "ad9361-phy"), 2e6);

	iio_channel *rx_i = iio_device_find_channel(iio_context_find_device(ctx, "cf-ad9361-lpc"), "voltage0", false);
	iio_channel *rx_q = iio_device_find_channel(iio_context_find_device(ctx, "cf-ad9361-lpc"), "voltage1", false);
	iio_channel_enable(rx_i);
	iio_channel_enable(rx_q);

	iio_buffer  *rxbuf = iio_device_create_buffer(iio_context_find_device(ctx, "cf-ad9361-lpc"), BUF_SIZE, false);;
	
	while ((do_exit == false)) {

		iio_buffer_refill(rxbuf);
			
		{
			std::lock_guard<std::mutex> lock(mutex);
			
			size_t buf_tail=(buf_head+buf_count)%BUF_LEN;
			int16_t* p_dat = (int16_t*)iio_buffer_first(rxbuf, rx_i);
			std::memcpy(buffer[buf_tail],p_dat,BUF_SIZE*sizeof(int16_t)*2);
		
			if(buf_count==BUF_LEN){
				buf_head=(buf_head+1)%BUF_LEN;
			}else{		
				buf_count++;
			}
			
			cond.notify_one();

		}
	}

	if (rxbuf)
		iio_buffer_destroy(rxbuf);

	if (rx_i)
		iio_channel_disable(rx_i);

	if (rx_q)
		iio_channel_disable(rx_q);

	if (ctx)
		iio_context_destroy(ctx);

}

int main(int argc, char **argv) {

	int c, option_index = 0, backend=0;

	std::string uri,hostname,ipaddr, bleaddr;
	ipaddr = "192.168.2.10";
	uint64_t ac_addr = LE_ADV_AA;
	while ((c = getopt_long(argc, argv, "+hn:u:c:o:a:s:",options, &option_index)) != -1) {
		switch (c) {
			case 'h':
				usage();
				return EXIT_SUCCESS;
			case 'n':
				if (backend != 0) {
					fprintf(stderr, "-n and -u are mutually exclusive\n");
					return EXIT_FAILURE;
				}
				backend = 1;
				hostname = optarg;
				break;
			case 'u':
				if (backend != 0) {
					fprintf(stderr, "-n and -u are mutually exclusive\n");
					return EXIT_FAILURE;
				}
				backend = 2;
				uri = optarg;
				break;
			case 'c':
				channel= atoi(optarg);
				break;
			case 'a':
				bleaddr = optarg;
				break;
			case 's':
				ipaddr = optarg;
				break;
			default:
				usage();
				return EXIT_FAILURE;
		}

	}

	if (backend == 0) {

		ctx = autodetect_context();

	}
	else if (backend == 1)
	{
		ctx = iio_create_network_context(hostname.c_str());

	}
	else if (backend == 2)
	{
		ctx = iio_create_context_from_uri(uri.c_str());
	}
	else {

		ctx = iio_create_default_context();	
	}

	if (!ctx) {
		char buf[1024];

		iio_strerror(errno, buf, sizeof(buf));
		fprintf(stderr, "Unable to create IIO context: %s\n",buf);
		return EXIT_FAILURE;
	}

#ifdef _MSC_VER
	SetConsoleCtrlHandler((PHANDLER_ROUTINE)sighandler, TRUE);
#else
	signal(SIGINT, &sigint_callback_handler);
	signal(SIGILL, &sigint_callback_handler);
	signal(SIGFPE, &sigint_callback_handler);
	signal(SIGSEGV, &sigint_callback_handler);
	signal(SIGTERM, &sigint_callback_handler);
	signal(SIGABRT, &sigint_callback_handler);
#endif

	decoder = new bledecoder();
	sender = new packetsender(ipaddr);

	decoder->set_access_addr(ac_addr);
	decoder->set_channel(channel);
	decoder->set_callback(PacketCallback);

	std::thread recv_threadid(thread_recv);
	std::thread decode_threadid(thread_decode);
	recv_threadid.join();
	decode_threadid.join();

	return 0;
}
